package com.pocket.demo.core.security.oauth2;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.web.SecurityFilterChain;

import javax.sql.DataSource;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.UUID;


/**
 * 参考：
 *      https://github.com/spring-projects/spring-authorization-server/tree/main/samples
 *      https://blog.csdn.net/llg___/article/details/136802996
 * 说明：
 *      proxyBeanMethods = true  : Full 全模式，配置类中注入导容器中的 Bean 是单例的；
 *      proxyBeanMethods = false : Lite 轻量级模式，配置类中注入导容器中的 Bean 是多例的；
 * @author zhaozhile
 */
@Configuration(proxyBeanMethods = true)
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {

    private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";

    @Autowired
    private DataSource dataSource;

    @Bean
    public JdbcOperations jdbcOperations() {
        return new JdbcTemplate(dataSource);
    }

    /**
     * 1. 配置 授权服务器 相关端点
     * @return
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        // return AuthorizationServerSettings.builder().build();
        // 下面是 AuthorizationServerSettings 的默认配置，此处重写进行属性说明
        return AuthorizationServerSettings.builder()
                // 定义授权服务器唯一标识。在颁发令牌时issuer会被设置到JWT中。同时也用来构造OIDC中的发现文档地址。
                .issuer("http://127.0.0.1:8888")
                // 如下配置是 builder() 方法中的默认配置，再次列出注释下
                // 授权端点
                .authorizationEndpoint("/oauth2/authorize")
                // 授权码端点
                .deviceAuthorizationEndpoint("/oauth2/device_authorization")
                // 授权码验证端点
                .deviceVerificationEndpoint("/oauth2/device_verification")
                // 令牌端点
                .tokenEndpoint("/oauth2/token")
                // JWK 端点
                .jwkSetEndpoint("/oauth2/jwks")
                // 令牌 注销端点
                .tokenRevocationEndpoint("/oauth2/revoke")
                // 令牌 验证端点
                .tokenIntrospectionEndpoint("/oauth2/introspect")
                // openid connect 注册端点
                .oidcClientRegistrationEndpoint("/connect/register")
                // openid connect 用户信息端点
                .oidcUserInfoEndpoint("/userinfo")
                // openid connect 登出端点
                .oidcLogoutEndpoint("/connect/logout")
                .build();
    }

    /**
     * 2. 配置客户端详情存储方式
     *      默认实现：InMemoryRegisteredClientRepository、JdbcRegisteredClientRepository
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) {
        JdbcRegisteredClientRepository jdbcRegister = new JdbcRegisteredClientRepository(jdbcOperations);
        // 添加默认的 RegisteredClient 到数据库
        RegisteredClient defaultRegisteredClient = registeredClient();
        if (jdbcRegister.findByClientId(defaultRegisteredClient.getClientId()) == null) {
            jdbcRegister.save(defaultRegisteredClient);
        }
        return jdbcRegister;
    }

    /**
     *  3. 配置Token存储方式 为 数据库存储
     *      默认实现：InMemoryOAuth2AuthorizationService、JdbcOAuth2AuthorizationService
     */
    @Bean
    public OAuth2AuthorizationService oAuth2AuthorizationService(JdbcOperations jdbcOperations) {
        JdbcOAuth2AuthorizationService johnAuthorizationService = new JdbcOAuth2AuthorizationService(jdbcOperations(), registeredClientRepository(jdbcOperations));
        return johnAuthorizationService;
    }

    /**
     * jwt 编码器。用于签名访问令牌（access token），授权服务 用
     */
    //@Bean
    public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
        return new NimbusJwtEncoder(jwkSource);
    }

    /**
     * 用于生成包含 RSA密钥对 的不可变 JWKSet 对象，用于 签名JWT、验证JWT 。
     * 授权服务器、资源服务器 通过
     * @return
     */
    //@Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }


    /**
     * 配置 oauth2 协议端点的过滤器链
     */
    //@Bean
    //@Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        // 授权服务器配置器、授权服务器端点请求匹配器
//        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
//        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class);
        // OAuth2AuthorizationRequestRedirectFilter和OAuth2LoginAuthenticationFilter
        //http.oauth2Login(Customizer.withDefaults());
        return http.build();
    }


//    @Bean
//    SupplierClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
//        List<ClientRegistration> registrations = new ArrayList<>(new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values());
//        return new SupplierClientRegistrationRepository((Supplier) () -> registrations);
//    }



    // 客户端认证逻辑处理拦截器 OAuth2ClientAuthenticationFilter
    // 核心逻辑通过 authenticationConverter 从 request 中解析出客户端认证信息，构建成 Authentication；
    // 再通过 authenticationManager 对 Authentication 进行认证。不同的解析方式实际上就代表不同的认证方式（传参不同）。
    // 不同的 authenticationConverter 解析不同的认证信息。authenticationManager 中不同的 AuthenticationProvider 处理不同的认证对象。


    private RegisteredClient registeredClient(){
        ClientSettings clientSettings = ClientSettings.builder()
                // 是否需要用户授权确认
                .requireAuthorizationConsent(false)
                // 当授权类型为 client_credentials 且 client-authentication-method=client_secret_jwt，使用此处配置的算法
                .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256)
                // 当授权类型为 authorization_code 且 client-authentication-method=none 时自动开启
                .requireProofKey(false)
                // 当使用非对称加密是，服务端通过此 URL 获取客户端公钥，来解密客户端提交的信息；验证通过后服务端用自己的私钥签名并加密token颁发给客户端
                //.jwkSetUrl("http://127.0.0.1:8080/jwks")
                .build();

        TokenSettings tokenSetting = TokenSettings.builder()
                .authorizationCodeTimeToLive(Duration.ofSeconds(60))
                .accessTokenTimeToLive(Duration.ofHours(2))
                .refreshTokenTimeToLive(Duration.ofHours(5))
                // refreshToken 是否能被重复使用，true - 可以，false - 每次返回新的 refreshToken
                .reuseRefreshTokens(false)
                .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
                .build();

        return RegisteredClient
                // 唯一标识
                .withId(UUID.randomUUID().toString())
                // 客户端标识
                .clientId("pocket-client")
                // 客户端密钥
                .clientSecret("{noop}secret")
                /**
                 * 客户端认证方式：
                 *      client_secret_basic: 客户端使用 client_id、client_secret 通过请求头方式认证：Authorization: Basic {Base64.encode(client_id:client_secret)}（不建议使用，安全性低）；
                 *      client_secret_post: 客户端使用 client_id、client_secret 通过表单方式认证（不建议使用，安全性低）；
                 *      client_secret_jwt: 客户端使用 client_secret 通过 HMAC 算法生成 jwt 进行认证；
                 *      private_key_jwt: 客户端生成密钥对，将公钥发送给授权服务器，客户端使用私钥生成 jwt，授权服务器通过客户端的公钥验证 jwt；
                 *      none:
                 *  参考：https://blog.csdn.net/qq_31772441/article/details/126576428
                 */
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                /**
                 * 授权类型，默认包括：
                 *      authorization_code：授权码模式
                 *      refresh_token：刷新令牌
                 *      client_credentials：客户端模式
                 *      urn:ietf:params:oauth:grant-type:jwt-bearer：
                 *      urn:ietf:params:oauth:grant-type:device_code：
                 */
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)

                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
                .postLogoutRedirectUri("http://127.0.0.1:8080/authorized")


                /**
                 * 资源授权范围
                 */
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE)
                .scope("message.read")
                .scope("message.write")
                .clientSettings(clientSettings)
                .tokenSettings(tokenSetting)
                .build();
    }


    // 初始化手动配置
    /**
     * 基于RSA算法生成长度为 2048 的 RSA公钥和私钥 密钥对，公钥会被公布
     */
    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        }
        catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    public static void main(String[] args){
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        System.out.println(publicKey);
        System.out.println(privateKey);
    }



}
